Skip to content

feat: add Register Update dialog to UpdatesDashboard#70

Merged
bburda merged 6 commits intomainfrom
feat/register-update-dialog
Apr 19, 2026
Merged

feat: add Register Update dialog to UpdatesDashboard#70
bburda merged 6 commits intomainfrom
feat/register-update-dialog

Conversation

@bburda
Copy link
Copy Markdown
Contributor

@bburda bburda commented Apr 15, 2026

Summary

Adds a Register Update button to the Software Updates dashboard toolbar, opening a dialog that POSTs to SOVD /updates. Closes #69.

The dialog collects id (required), update_name (defaults to id when blank), automated (checkbox), and free-form JSON additional metadata. Client-side validates required id and JSON parse; merges metadata extras into the request body before submitting. A new registerUpdate Zustand store action handles the HTTP call.

Generic by design: no vendor-specific fields at the top level - all vendor extras go through the JSON metadata field, so the same dialog works for Uptane, Mender, and any other UpdateProvider plugin.


Issue


Type

  • Bug fix
  • New feature
  • Breaking change
  • Documentation only

Testing

  • New unit tests in src/components/RegisterUpdateDialog.test.tsx (4 cases: render, required id, JSON parse, merged body submit).
  • Existing UpdatesDashboard.test.tsx suite (11 tests) passes - new toolbar button does not disturb existing behavior.
  • Local typecheck (npm run typecheck) clean.

Checklist

  • Linting passes (npm run lint)
  • Build succeeds (npm run build)
  • No behavior / API changes that need doc updates (new UI component, additive)

Base branch

This PR targets fix/uds-data-tab-rendering (PR #68) rather than main, so it stacks on top of that Data-tab hardening work. Rebase onto main is trivial once #68 merges.

Copilot AI review requested due to automatic review settings April 15, 2026 20:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “Register Update” entry point to the Software Updates dashboard so operators can create new updates via POST /updates from the UI, backed by a new Zustand store action.

Changes:

  • Added registerUpdate action to the Zustand store to POST update registration payloads.
  • Added RegisterUpdateDialog component and wired it into UpdatesDashboard via a new toolbar button.
  • Added new Label/Checkbox UI primitives and unit tests for the dialog.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lib/store.ts Adds registerUpdate store action calling client.POST('/updates', …)
src/components/UpdatesDashboard.tsx Adds “Register Update” button and renders the dialog
src/components/RegisterUpdateDialog.tsx Implements the registration dialog UI, validation, and payload construction
src/components/RegisterUpdateDialog.test.tsx Adds unit tests for dialog validation and submission behavior
src/components/ui/label.tsx Introduces a Label primitive used by the dialog
src/components/ui/checkbox.tsx Introduces a Checkbox primitive used by the dialog

Comment thread src/components/RegisterUpdateDialog.tsx Outdated
Comment thread src/lib/store.ts Outdated
Comment thread src/components/ui/label.tsx
Comment thread src/components/ui/checkbox.tsx
Comment thread src/components/RegisterUpdateDialog.tsx Outdated
@bburda bburda self-assigned this Apr 19, 2026
@bburda bburda requested a review from mfaferek93 April 19, 2026 11:09
Base automatically changed from fix/uds-data-tab-rendering to main April 19, 2026 12:26
bburda added 3 commits April 19, 2026 14:29
Add RegisterUpdateDialog component with id, name, automated, and JSON
metadata fields. Includes client-side validation (required id, JSON
parse check) and merges metadata extras into the request body. Add
registerUpdate store action and install shadcn label/checkbox primitives.
Wire RegisterUpdateDialog into UpdatesDashboard with a toolbar button.
Default update_name to id when the name field is left blank so the
gateway field requirement is always satisfied.
…dowing

- useEffect resets id/name/automated/metadata/error when dialog closes,
  so reopening starts clean instead of showing stale inputs or errors.
- Strip id/update_name/automated from the JSON metadata object and
  spread extras before the UI-controlled fields so user metadata
  cannot override validated top-level values.
- Add regression test covering the reserved-key stripping.
@bburda bburda force-pushed the feat/register-update-dialog branch from 86faee6 to e495d64 Compare April 19, 2026 12:33
@bburda bburda requested a review from Copilot April 19, 2026 12:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/components/UpdatesDashboard.tsx:65

  • handleRegister performs a mutation but doesn’t use the existing actionAbortRef cancellation pattern used by prepare/execute/automated/delete. If the dashboard unmounts while a register request is in-flight, it can still call toast.* and refresh() afterward (the comment on lines 57-59 suggests the intent is to avoid stale toasts/state updates on unmounted components). Consider extending registerUpdate to accept an optional AbortSignal and wiring actionAbortRef.current?.signal through, with the same signal.aborted/AbortError guards used in handleAction.
    const handleRegister = async (body: { id: string; [key: string]: unknown }) => {
        try {
            await registerUpdate(body);
            toast.success(`Registered ${body.id}`);
            refresh();
        } catch (e) {
            const msg = e instanceof Error ? e.message : String(e);
            toast.error(`Register failed: ${msg}`);
            throw e;
        }
    };

    // AbortController for mutation actions (prepare/execute/automated/delete).
    // Aborted on unmount so in-flight requests don't resolve into setBusyIds
    // on an unmounted component or issue stale toast notifications.
    const actionAbortRef = useRef<AbortController | null>(null);
    useEffect(() => {
        const controller = new AbortController();
        actionAbortRef.current = controller;
        return () => controller.abort();
    }, []);

Comment thread src/components/RegisterUpdateDialog.tsx
Comment thread src/lib/store.ts Outdated
…bmitting

- store.registerUpdate now takes an optional AbortSignal and forwards
  it to client.POST('/updates'), matching the prepare/execute/automated/
  delete pattern.
- UpdatesDashboard.handleRegister threads actionAbortRef.current?.signal
  through and guards toast.success/refresh with signal.aborted so a
  late-resolving register cannot fire stale toasts or refresh after
  unmount.
- RegisterUpdateDialog now ignores Escape / pointer-down-outside /
  onOpenChange(false) and disables Cancel while submitting, so a
  slow submit cannot close a dialog the user has since reopened.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread src/components/UpdatesDashboard.tsx
Comment thread src/lib/store.ts
Comment thread src/components/UpdatesDashboard.tsx
Comment thread src/lib/store.ts
Comment thread src/components/RegisterUpdateDialog.tsx Outdated
Comment thread src/components/RegisterUpdateDialog.tsx Outdated
Comment thread src/components/UpdatesDashboard.tsx Outdated
Comment thread src/components/RegisterUpdateDialog.test.tsx
bburda added 2 commits April 19, 2026 15:44
- Drop the redundant toast.error in handleRegister: the dialog already
  surfaces the rejection as an inline error, so users were seeing the
  same failure twice. The throw is kept so the dialog stays open for
  retry (setError runs in the dialog's catch).
- Hide the Register Update button when the gateway reports the updates
  API as unavailable (notAvailable/501) so the UI does not expose a
  flow that can only fail.
- Hoist RegisterUpdateDialog render out of the five conditional
  branches (loading/notAvailable/error/empty/list) into the shared
  shell via a local 'body' variable.
- Always send automated=true|false instead of omitting it when false,
  so the wire body has a stable shape regardless of UI state.
- Add role=alert, aria-invalid, and aria-describedby so validation
  errors are announced by screen readers and associated with the id
  and metadata inputs.
- Document why the client.POST('/updates', { body: body as never })
  cast is intentional (gateway keeps the schema open for vendor
  extras, endpoint not yet in the generated client).
- Extend RegisterUpdateDialog tests: inline error on submit rejection,
  non-object JSON rejection parameterised across string/array/null/
  number, and automated=false when unchecked.
- RegisterUpdateDialog now renders a DialogDescription (removes the
  Radix 'Missing Description / aria-describedby' warning that was
  firing on every render and every test) and shows a Loader2 spinner
  with 'Registering...' label while the submit is in flight, so the
  user gets visual feedback during the network call instead of just
  a silently disabled button.
- UpdateCard: introduce displayProgress() which returns 100 whenever
  status.status === 'completed', regardless of whether the gateway
  dropped the progress field or left it below 100. Applied to both
  the main bar and each sub_progress row. Prevents the bar from
  disappearing or freezing mid-way at end-of-update.
- Tests: assert the 100% bar appears when completed has no progress
  field and that mid-progress values snap to 100% when status flips
  to completed.
@bburda bburda merged commit e2bd406 into main Apr 19, 2026
3 checks passed
@bburda bburda deleted the feat/register-update-dialog branch April 19, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Register Update dialog to UpdatesDashboard

3 participants